package com.zyl.file.service.impl;

import com.baomidou.lock.annotation.Lock4j;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.SftpException;
import com.zyl.file.mapper.StEquipmentMapper;
import com.zyl.file.po.StEquipment;
import com.zyl.file.result.Result;
import com.zyl.file.result.ResultCode;
import com.zyl.file.service.LocalSplitFileService;
import com.zyl.file.util.DateUtils;
import com.zyl.file.util.FtpUtils;
import com.zyl.file.util.IoUtils;
import com.zyl.file.vo.SplitUploadVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.InputStream;
import java.io.SequenceInputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Vector;

/**
 * @author zyl
 * @date 2023/8/11 9:53:11
 * @title
 * @description
 */
@Slf4j
@Service
public class LocalSplitFileServiceImpl implements LocalSplitFileService {

    /**
     * 分片文件临时存储文件夹
     */
    private String temporaryFolder = "temporaryFolder";

    @Resource
    private StEquipmentMapper stEquipmentMapper;


    //需设计一个额外逻辑
    //如文件一直处于续传状态，多久直接删除已存在的分片文件，使之下次重传
    //一直存在分片文件，造成数据冗余，空间浪费

    @Override
    public Result uploadSplit(byte[] splitFile, String fileName, SplitUploadVO splitUploadVO, Integer partNumber) {
        //通过指定设备id，来拿到对应的存储路径
        StEquipment stEquipment = stEquipmentMapper.selectOne(new LambdaQueryWrapper<StEquipment>().eq(StEquipment::getId, splitUploadVO.getEquipmentId()));
        ChannelSftp channelSftp = null;
        try {
            channelSftp = FtpUtils.getConnect(stEquipment.getDomainName().split(":")[0], Integer.valueOf(stEquipment.getDomainName().split(":")[1]), stEquipment.getAccessKey(), stEquipment.getAccessSecret());
        } catch (Exception e) {
            e.printStackTrace();
        }

        if (null == channelSftp) {
            return Result.error("无对应存储设备连接", ResultCode.PARAM_ERROR);
        }
        String url = stEquipment.getBasePath() + File.separator + temporaryFolder + File.separator
                + splitUploadVO.getBusiBatchNo() + File.separator
                + splitUploadVO.getIdentifier() + File.separator;
        String partFileName = partNumber + "-" + fileName;
        try {
            try {
                channelSftp.lstat(url + partFileName);
                return Result.success("");
            } catch (SftpException e) {
                FtpUtils.mkdirDir(channelSftp, url.split(File.separator), "", url.split(File.separator).length, 0);
            }
            FtpUtils.upload(channelSftp, url, splitFile, partFileName);
            channelSftp.disconnect();
            return Result.success("");
        } catch (Exception e) {
            return Result.error("上传失败", ResultCode.SYSTEM_ERROR);
        }
    }

    @Override
    @Lock4j(keys = {"#splitUploadVO.identifier"})
    public Result merge(SplitUploadVO splitUploadVO) {
        //通过指定设备id，来确定存储路径
        StEquipment stEquipment = stEquipmentMapper.selectOne(new LambdaQueryWrapper<StEquipment>().eq(StEquipment::getId, splitUploadVO.getEquipmentId()));
        ChannelSftp channelSftp = null;
        try {
            channelSftp = FtpUtils.getConnect(stEquipment.getDomainName().split(":")[0], Integer.valueOf(stEquipment.getDomainName().split(":")[1]), stEquipment.getAccessKey(), stEquipment.getAccessSecret());
        } catch (Exception e){
            log.info("服务器连接失败。");
            return Result.error("连接出错，请核对相关配置", ResultCode.SYSTEM_ERROR);
        }
        String url = stEquipment.getBasePath() + File.separator + temporaryFolder + File.separator
                + splitUploadVO.getBusiBatchNo() + File.separator
                + splitUploadVO.getIdentifier() + File.separator;
        String lastFile = stEquipment.getBasePath() + File.separator + DateUtils.getDate();
        try {
            //每隔1S重试一次，10次后url中还没有文件则返回失败
            Boolean isExist = true;
            Integer count = 10;
            while (isExist && count > 0) {
                try {
                    --count;
                    Thread.sleep(1000);
                    channelSftp.stat(url);
                    isExist = false;
                } catch (Exception e) {
                }
            }
            if(isExist){
                return Result.error("文件合并失败："+url,ResultCode.SYSTEM_ERROR);
            }
            Vector ls = channelSftp.ls(url);
            Iterator iterator = ls.iterator();
            Map<String,InputStream> splitFileMap = new HashMap<>();
            while (iterator.hasNext()) {
                ChannelSftp.LsEntry file = (ChannelSftp.LsEntry) iterator.next();
                //文件名称
                String fileName = file.getFilename();
                if (fileName.contains("-")) {
                    splitFileMap.put(fileName,channelSftp.get(url + fileName));
                }
            }
            //对分片文件按照1、2、3...排序
            Vector<InputStream> vector = sortSplitFile(splitFileMap);
            SequenceInputStream sequenceInputStream = new SequenceInputStream(vector.elements());
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int bytesRead;
            while ((bytesRead = sequenceInputStream.read(buffer)) != -1) {
                byteArrayOutputStream.write(buffer, 0, bytesRead);
            }
            byteArrayOutputStream.close();
            FtpUtils.upload(channelSftp, lastFile, byteArrayOutputStream.toByteArray(), splitUploadVO.getFileName());
            log.info("合并完成!");
            //删除分片
            delSplit(channelSftp, url);
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            channelSftp.disconnect();
        }
        return Result.success("");
    }

    @Override
    public Result replaceFile(InputStream newFileStream, String url, Long equipmentId) {
        StEquipment stEquipment = stEquipmentMapper.selectById(equipmentId);
        ChannelSftp channelSftp = null;
        try {
            channelSftp = FtpUtils.getConnect(stEquipment.getDomainName().split(":")[0], Integer.valueOf(stEquipment.getDomainName().split(":")[1]), stEquipment.getAccessKey(), stEquipment.getAccessSecret());
            FtpUtils.upload(channelSftp, FilenameUtils.getPath(url), IoUtils.input2byte(newFileStream), FilenameUtils.getBaseName(url)
                    + "." + FilenameUtils.getExtension(url));
            log.info("文件替换成功！");
        } catch (Exception e) {
            log.error("文件操作失败：{}", e.getMessage());
            return Result.error("文件操作失败", ResultCode.SYSTEM_ERROR);
        }finally {
            channelSftp.disconnect();
        }
        return Result.success("");
    }

    private void delSplit(ChannelSftp channelSftp, String url) {
        try {
            // 列出文件夹中的所有文件
            Vector ls = channelSftp.ls(url); // url是文件夹的路径
            Iterator iterator = ls.iterator();

            while (iterator.hasNext()) {
                ChannelSftp.LsEntry file = (ChannelSftp.LsEntry) iterator.next();
                // 获取文件名称
                String fileName = file.getFilename();

                // 检查是否是文件而不是子目录
                if (!file.getAttrs().isDir()) {
                    // 使用rm方法删除文件
                    channelSftp.rm(url + fileName);
                    log.info("删除文件: {}", fileName);
                }
            }
            channelSftp.rmdir(url);
            log.info("文件夹及其所有文件已删除: {}", url);
        } catch (Exception e) {
            log.info("删除分片文件失败: {}", url);
            e.printStackTrace();
        }
    }

    /**
     * 对分片文件按照1、2、3...排序，属顺序不对拼接出的文件会损坏
     * @param map
     * @return
     */
    private Vector<InputStream> sortSplitFile(Map<String, InputStream> map){
        // 将 Map 转换为 List
        List<Map.Entry<String, InputStream>> list = new ArrayList<>(map.entrySet());

        // 对 List 进行排序，排序规则是按照键的字典顺序排序
        Collections.sort(list, new Comparator<Map.Entry<String, InputStream>>() {
            @Override
            public int compare(Map.Entry<String, InputStream> o1, Map.Entry<String, InputStream> o2) {
                int num1 = Integer.parseInt(o1.getKey().split("-")[0]);
                int num2 = Integer.parseInt(o2.getKey().split("-")[0]);
                return Integer.compare(num1, num2);
            }
        });

        // 创建一个新的 Vector<InputStream>，并按照排序后的顺序填充
        Vector<InputStream> sortedStreams = new Vector<>();
        for (Map.Entry<String, InputStream> entry : list) {
            sortedStreams.add(entry.getValue());
        }

        return sortedStreams;
    }

}
